﻿using Cysharp.Threading.Tasks;
using LockStep.Server;
using Net.Event;
using Net.Helper;
using Net.SceneUtility;
using Net.Server;
using Net.Share;
using Net.System;
using System;
using System.Collections.Generic;
using System.IO;

namespace Example2
{
    /// <summary>
    /// 服务器组件,  (案例代码)
    /// </summary>
    public class Service : WebServer<Client, Scene>
    {
        protected override void OnStartupCompleted()
        {
            base.OnStartupCompleted();
#if !UNITY_EDITOR
            var path = AppDomain.CurrentDomain.BaseDirectory + "/../Data/";
#else
            var path = UnityEngine.Application.dataPath + "/GameDesigner/Example/ExampleServer~/bin/Debug/Data/";
#endif
            var files = Directory.GetFiles(path, "*.bytes");
            foreach (var flie in files)
            {
                var mapData = MapData.ReadFileData(flie);
                var scene = CreateScene(mapData.name);
                scene.mapData = mapData;
                scene.Init();
                NDebug.Log("创建地图:" + scene.Name);
            }
            MainSceneName = "Battle";//指定你的主战斗场景名称, 根据unity的主战斗场景名称设置
            NDebug.Log("主地图名称:Battle");
        }

        /// <summary>
        /// 当添加默认场景的时候
        /// </summary>
        /// <returns></returns>
        protected override void OnAddDefaultScene()
        {
            //我们不添加默认场景
        }

        /// <summary>
        /// 当客户端登录成功时要添加到主场景时
        /// </summary>
        /// <param name="client"></param>
        protected override void OnAddPlayerToScene(Client client)
        {
            //base.OnAddPlayerToScene(client); //如果不允许登录成功加入主大厅场景, 注释这行代码即可
        }

        protected override void OnOperationSync(Client client, in OperationList list)
        {
            base.OnOperationSync(client, list);//当操作同步处理, 帧同步或状态同步通用
        }

        /// <summary>
        /// 当我们接收到客户端 刚连接服务器后, 发送的第一个请求时, 我们应该处理登录或注册方法
        /// </summary>
        protected override bool OnUnClientRequest(Client unClient, IRpcModel model)
        {
            //我们解析了客户端数据, 如果得到的是 注册 或 登录 这样的函数才进行处理
            if (model.Protocol == "Register".CRCU32())
            {
                _ = Register(unClient, model.AsString, model.AsString);
            }
            else if (model.Protocol == "Login".CRCU32())
            {
                _ = Login(unClient, model.AsString, model.AsString);
            }
            return false;
        }

        /// <summary>
        /// 当开始调用 rpc标签的方法 时, 我们重写这个方法, 我们自己指定应该调用的方法, 这样会大大提高服务器效率
        /// </summary>
        /// <param name="client"></param>
        /// <param name="model"></param>
        protected override void OnRpcExecute(Client client, IRpcModel model)
        {
            base.OnRpcExecute(client, model); //反射调用rpc
        }

        /// <summary>
        /// 当客户端注册的时候, 我们应该检查数据库 账号是否存在, 如果不存在, 则可以注册新的账号, 反则应该提示客户端注册失败
        /// </summary>
        private async UniTask Register(Client unClient, string acc, string pwd)
        {
            var cache = Example2DB.I.UserDatas.GetOrCreate(acc); //获取缓存key
            cache.Locking.Enter(); //锁定缓存 --在多线程并行状态下, 防止数据冲突, 这里是锁单个key, 能提高效率, 如果你直接lock(this)锁整个对象, 效率会低很多
            var data = await Example2DB.I.QueryAsync<UserinfoData>($"account = '{acc}'");
            if (data == null)
            {
                data = new UserinfoData
                {
                    Account = acc,
                    Password = pwd,
                    Health = 100,
                    HealthMax = 100,
                    MoveSpeed = 3f
                };
                var affectedRows = await data.NewRowAsync();
                if (affectedRows <= 0)
                    Call(unClient, "RegisterCallback", "注册失败!");
                else
                    Call(unClient, "RegisterCallback", "注册成功！");
            }
            else
            {
                Call(unClient, "RegisterCallback", "注册失败!");
            }
            cache.Locking.Exit(); //解锁缓存
        }

        /// <summary>
        /// 当客户端发送的是登录方法的时候, 我们应该检查数据库是否有客户端所指定的账号, 如果有则判断 客户端输入的密码是否正确,
        /// 如果正确, 则提示登录成功, 并且返回玩家对象 , 返回的对象会添加到在线客户端字典列表里, 这是服务器内部管理的对象
        /// </summary>
        [Rpc(NetCmd.SafeCallHash)]
        private async UniTask Login(Client unClient, string acc, string pwd)
        {
            var cache = Example2DB.I.UserDatas.GetOrCreate(acc);
            var data = await cache.QueryOrGetAsync(async () => await Example2DB.I.QueryAsync<UserinfoData>($"account = '{acc}'"));
            if (data == null)
            {
                Call(unClient, "LoginCallback", false, "账号不存在!");
                return;
            }
            if (data.Password != pwd)
            {
                Call(unClient, "LoginCallback", false, "账号或密码错误!");
                return;
            }
            unClient.Name = acc;
            unClient.PlayerId = acc; //赋值为账号, 当登录时如果这个账号有登录, 则会先退出上个账号再登录当前账号
            unClient.data = data;
            LoginHandler(unClient);
            Call(unClient, "LoginCallback", true, "登录成功!");
        }

        public override void OnSignOut(Client client, bool passive)
        {
            Call(client, "BackLogin", "你的账号在其他地方被登录!"); //在客户端热更新工程的MsgPanel类找到
        }

        /// <summary>
        /// 客户端主动发起退出登录请求
        /// </summary>
        /// <param name="client"></param>
        [Rpc(NetCmd.SafeCallHash)]
        internal void LogOut(Client client)
        {
            Call(client, "LogOut");//在客户端热更新工程的MsgPanel类找到
            SignOut(client);
        }

        [Rpc(NetCmd.SafeCallHash)]
        internal void EnterGameScene(Client client, string sceneName)
        {
            var scene = EnterScene(client, sceneName);
            Response(client, "EnterGameScene", scene != null ? 1 : -1);
        }

        protected override void OnRemoveClient(Client client)
        {
            base.OnRemoveClient(client);//当客户端断开连接处理
        }
    }
}